Skip to content

Log why a day is skipped when HR samples are insufficient (#714) - #720

Merged
ryanbr merged 1 commit into
mainfrom
diag/sleep-skip-log-714
Jul 22, 2026
Merged

ryanbr merged 1 commit into
mainfrom
diag/sleep-skip-log-714

Conversation

@ryanbr

@ryanbr ryanbr commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

When the analytics pass skips a day for having fewer than 200 HR samples, the strap log currently shows sleep day=… totalSleepMin=nil matched=0 with no indication of why — indistinguishable from "stager ran and found no sleep." This adds one diagnostic line per skipped day on both platforms:

sleep day=2026-07-20 SKIPPED hrSamples=0 (need ≥200)

Makes every "no sleep detected" report (#714) self-diagnosing from the export alone — no back-and-forth needed to determine "no data" vs "data but no sleep found."

Changes

  • IntelligenceEngine.kt: diag() line before the continue in the HR-gate skip
  • IntelligenceEngine.swift: matching diag() line in the guard else branch

Test plan

  • ./gradlew compileFullDebugKotlin — compiles
  • No test changes needed — this is a log-only addition with no behavior change
  • Cross-platform parity: identical log format on both platforms

When a day has fewer than 200 HR samples, the analytics pass silently
skips it. The sleep line still emits totalSleepMin=nil matched=0 but
nothing distinguishes "stager ran, found nothing" from "no data to
stage." A one-line diagnostic on each platform now makes every
"no sleep" report self-diagnosing from the strap log alone.
@ryanbr
ryanbr merged commit 2bc9c24 into main Jul 22, 2026
1 check passed
@jbergler

Copy link
Copy Markdown

@ryanbr FYI I think this creates some build issues for the iOS app.

~/co/noop/Strand/Data/IntelligenceEngine.swift:544:21 Cannot find 'diag' in scope

ryanbr pushed a commit that referenced this pull request Jul 22, 2026
135c724 (PR #720, #714) added a `diag("sleep day=… SKIPPED …")` call to
the Swift scan loop to mirror the Kotlin `diag` sink, but Swift has no
such function — `diagnosticSink` is the equivalent, and it's MainActor-
isolated while this call site runs inside `Task.detached`, off the main
actor. The app target (NOOPiOS/Strand) doesn't build under default CI
(app-build.yml is disabled), so this shipped without failing a check.

Collect the skipped-day lines in the detached loop alongside the other
per-day diagnostics (rhrLine, sleepTrace, hrvTrace, stepsTrace) and
replay them through `diagnosticSink` back on the main actor, in the
same always-on (untagged) fashion as the Kotlin `diag` sink.

Verified: NOOPiOS and Strand (macOS) both build; Android
compileFullDebugKotlin already passed (Kotlin's diag call was valid).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JromGj5eBnU4Lxx7UfNsY5
ryanbr added a commit that referenced this pull request Jul 22, 2026
Fix iOS build break introduced in #720 (missing diag call)
@ryanbr
ryanbr deleted the diag/sleep-skip-log-714 branch August 2, 2026 07:41
simoncad7 pushed a commit to simoncad7/noop that referenced this pull request Aug 17, 2026
Log why a day is skipped when HR samples are insufficient (ryanbr#714)
simoncad7 pushed a commit to simoncad7/noop that referenced this pull request Aug 17, 2026
135c724 (PR ryanbr#720, ryanbr#714) added a `diag("sleep day=… SKIPPED …")` call to
the Swift scan loop to mirror the Kotlin `diag` sink, but Swift has no
such function — `diagnosticSink` is the equivalent, and it's MainActor-
isolated while this call site runs inside `Task.detached`, off the main
actor. The app target (NOOPiOS/Strand) doesn't build under default CI
(app-build.yml is disabled), so this shipped without failing a check.

Collect the skipped-day lines in the detached loop alongside the other
per-day diagnostics (rhrLine, sleepTrace, hrvTrace, stepsTrace) and
replay them through `diagnosticSink` back on the main actor, in the
same always-on (untagged) fashion as the Kotlin `diag` sink.

Verified: NOOPiOS and Strand (macOS) both build; Android
compileFullDebugKotlin already passed (Kotlin's diag call was valid).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JromGj5eBnU4Lxx7UfNsY5
simoncad7 pushed a commit to simoncad7/noop that referenced this pull request Aug 17, 2026
Fix iOS build break introduced in ryanbr#720 (missing diag call)
simoncad7 pushed a commit to simoncad7/noop that referenced this pull request Aug 17, 2026
Every diagnostic line ran LiveState.append(log:), which did an O(n)
Array.removeFirst once the 5k ring was full AND synchronously mirrored up
to 2000 lines to UserDefaults — on EVERY line. That per-line cost has
grown release over release as more diagnostics (offload/backfill, ryanbr#700,
ryanbr#714, ryanbr#720) funnel through this one sink, so a busy strap session gets
laggier the longer it stays connected.

Batch it: amortize the trim (let the ring overrun by a 256-line slack,
then trim in one shot) and mirror the durable tail every 32 lines instead
of every line, with an explicit flush on disconnect (clearBiometrics) so a
finished session's tail is always persisted for the scheduled export. What
gets stored/persisted is unchanged; only how often. Buffer stays hard-
bounded (<= maxLogLines + slack).

iOS-only: Android's log() already uses an O(1) ArrayDeque with no per-line
disk write, so no Kotlin twin is needed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants